// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/silent_sink_suspender.h"

#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"

namespace media {

SilentSinkSuspender::SilentSinkSuspender(
    AudioRendererSink::RenderCallback* callback,
    base::TimeDelta silence_timeout,
    const AudioParameters& params,
    const scoped_refptr<AudioRendererSink>& sink,
    const scoped_refptr<base::SingleThreadTaskRunner>& worker)
    : callback_(callback)
    , params_(params)
    , sink_(sink)
    , task_runner_(base::ThreadTaskRunnerHandle::Get())
    , silence_timeout_(silence_timeout)
    , fake_sink_(worker, params_)
    , sink_transition_callback_(
          base::Bind(&SilentSinkSuspender::TransitionSinks,
              base::Unretained(this)))
{
    DCHECK(params_.IsValid());
    DCHECK(sink_);
    DCHECK(callback_);
    DCHECK(task_runner_->BelongsToCurrentThread());
}

SilentSinkSuspender::~SilentSinkSuspender()
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    fake_sink_.Stop();
}

int SilentSinkSuspender::Render(base::TimeDelta delay,
    base::TimeTicks delay_timestamp,
    int prior_frames_skipped,
    AudioBus* dest)
{
    // Lock required since AudioRendererSink::Pause() is not synchronous, we need
    // to discard these calls during the transition to the fake sink.
    base::AutoLock al(transition_lock_);
    if (is_using_fake_sink_ && dest) {
        // Audio should be silent at this point, if not, it will be handled once the
        // transition to the fake sink is complete.
        dest->Zero();
        return dest->frames();
    }

    // When we're using the |fake_sink_| a null destination will be sent; we store
    // the audio data for a future transition out of silence.
    if (!dest) {
        DCHECK(is_using_fake_sink_);
        DCHECK_EQ(prior_frames_skipped, 0);
        // |delay_timestamp| contains the value cached at
        // |latest_output_delay_timestamp_|
        // so we simulate the real sink output, promoting |delay_timestamp| with
        // |elapsedTime|.
        DCHECK_EQ(delay_timestamp, latest_output_delay_timestamp_);
        base::TimeDelta elapsedTime = base::TimeTicks::Now() - fake_sink_transition_time_;
        delay_timestamp += elapsedTime;

        // If we have no buffers or a transition is pending, one or more extra
        // Render() calls have occurred in before TransitionSinks() can run, so we
        // store this data for the eventual transition.
        if (buffers_after_silence_.empty() || is_transition_pending_)
            buffers_after_silence_.push_back(AudioBus::Create(params_));
        dest = buffers_after_silence_.back().get();
    } else if (!buffers_after_silence_.empty()) {
        // Drain any non-silent transitional buffers before queuing more audio data.
        // Note: These do not skew clocks derived from frame count since we don't
        // issue Render() to the client when returning these buffers.
        DCHECK(!is_using_fake_sink_);
        buffers_after_silence_.front()->CopyTo(dest);
        buffers_after_silence_.pop_front();
        return dest->frames();
    }

    // Pass-through to client and request rendering.
    callback_->Render(delay, delay_timestamp, prior_frames_skipped, dest);

    // Check for silence or real audio data and transition if necessary.
    if (!dest->AreFramesZero()) {
        first_silence_time_ = base::TimeTicks();
        if (is_using_fake_sink_) {
            is_transition_pending_ = true;
            task_runner_->PostTask(
                FROM_HERE, base::Bind(sink_transition_callback_.callback(), false));
            return dest->frames();
        }
    } else if (!is_using_fake_sink_) {
        const base::TimeTicks now = base::TimeTicks::Now();
        if (first_silence_time_.is_null())
            first_silence_time_ = now;
        if (now - first_silence_time_ > silence_timeout_) {
            is_transition_pending_ = true;
            latest_output_delay_ = delay;
            latest_output_delay_timestamp_ = delay_timestamp;
            fake_sink_transition_time_ = now;
            task_runner_->PostTask(
                FROM_HERE, base::Bind(sink_transition_callback_.callback(), true));
        }
    }

    return dest->frames();
}

void SilentSinkSuspender::OnRenderError()
{
    callback_->OnRenderError();
}

void SilentSinkSuspender::TransitionSinks(bool use_fake_sink)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    // Ignore duplicate requests which can occur if the transition takes too long
    // and multiple Render() events occur.
    if (use_fake_sink == is_using_fake_sink_)
        return;

    if (use_fake_sink) {
        sink_->Pause();

        // |sink_| may still be executing Render() at this point or even sometime
        // after this point, so we must acquire the lock to make sure we don't have
        // concurrent Render() execution. Once |is_using_fake_sink_| is set to true,
        // calls from |sink_| will be dropped.
        {
            base::AutoLock al(transition_lock_);
            is_transition_pending_ = false;
            is_using_fake_sink_ = true;
        }
        fake_sink_.Start(
            base::Bind(base::IgnoreResult(&SilentSinkSuspender::Render),
                base::Unretained(this), latest_output_delay_,
                latest_output_delay_timestamp_, 0, nullptr));
    } else {
        fake_sink_.Stop();

        // Despite the fake sink having a synchronous Stop(), if this transition
        // occurs too soon after pausing the real sink, we may have pending Render()
        // calls from before the transition to the fake sink. As such, we need to
        // hold the lock here to avoid any races.
        {
            base::AutoLock al(transition_lock_);
            is_transition_pending_ = false;
            is_using_fake_sink_ = false;
        }
        sink_->Play();
    }
}

} // namespace content
